/**
 * @file wa_anim.c
 *
 */

 /*********************
  *      INCLUDES
  *********************/
#include "wa_conf.h"
#if USE_WA_ANIM != 0
#include "wa_anim.h"
#include "wa_task.h"
#include "wa_ll.h"
#include "wa_tick.h"
#include "wa_malloc.h"
/*********************
*      DEFINES
*********************/
#define WA_ANIM_RESOLUTION 1024
#define WA_ANIM_RES_SHIFT 10
#define WA_BEZIER_VAL_MAX 1024
#define WA_BEZIER_VAL_SHIFT 10
/**********************
*      TYPEDEFS
**********************/


/**********************
*  STATIC PROTOTYPES
**********************/
static int32_t wa_anim_map(int32_t x, int32_t min_in, int32_t max_in, int32_t min_out, int32_t max_out);
static uint32_t wa_anim_bezier3(uint32_t t, uint32_t u0, uint32_t u1, uint32_t u2, uint32_t u3);
static void wa_anim_task(void* task);
static void wa_anim_mark_list_change(void);
static void wa_anim_ready_handler(wa_anim* anim);
/**********************
*  STATIC VARIABLES
**********************/
static wa_ll top_anim_ll;
static wa_task* anim_task;

static uint32_t anim_last_timer_run;
static wa_result anim_list_changed;
static wa_result anim_run_round;
/**********************
*      MACROS
**********************/

/**********************
*   GLOBAL FUNCTIONS
**********************/

/**
* anim module init
*
*/
void wa_anim_module_init(void)
{
	wa_ll_init(&top_anim_ll, sizeof(wa_anim));
	anim_task = wa_task_create(wa_anim_task, WA_TASK_TYPE_TIMER);
	wa_task_set_timer_period(anim_task, WA_DISP_DEF_REFR_PERIOD);
	wa_task_stop_timer(anim_task);
	anim_last_timer_run = 0;
	anim_run_round = wa_nok;
}

/**
* create a anim
* @return anim
*/
wa_anim* wa_anim_create(void)
{
	wa_anim* anim;

	anim = wa_malloc(sizeof(wa_anim));
	if (anim == NULL)
	{
		return anim;
	}

	anim->var = NULL;

	anim->exec_cb = NULL;
	anim->start_cb = NULL;
	anim->finish_cb = NULL;
	anim->deleted_cb = NULL;
	anim->get_value_cb = NULL;
	anim->user_data = NULL;
	anim->path_cb = (wa_anim_path_cb)wa_anim_path_linear;

	anim->start_value = 0;
	anim->end_value = 100;
	anim->current_value = 0;
	anim->time_long = 500;
	anim->current_time = 0;
	anim->playback_delay = 0;
	anim->playback_time = 0;
	anim->repeat_delay = 0;
	anim->repeat_cnt = 1;

	anim->early_apply = wa_ok;
	anim->playback_now = wa_nok;
	anim->run_round = wa_nok;
	anim->start_cb_called = wa_nok;

	return anim;
}

/**
* delete a anim
* @param anim
*/
void wa_anim_del(wa_anim* anim)
{
	wa_free(anim);
}

/**
* start the anim
* @param anim
*/
void wa_anim_start(wa_anim* anim)
{
	wa_anim* new_anim;
	if (anim == NULL)
	{
		return;
	}
	new_anim = wa_ll_add_head(&top_anim_ll);
	if (new_anim == NULL)
	{
		return;
	}
	memcpy(new_anim, anim, sizeof(wa_anim));
	if (wa_ll_get_head(&top_anim_ll) != NULL)
	{
		anim_last_timer_run = wa_tick_get();
	}

	new_anim->run_round = anim_run_round;

	if (new_anim->early_apply == wa_ok)
	{
		if (new_anim->get_value_cb != NULL)
		{
			int32_t v_ofs = new_anim->get_value_cb(new_anim);
			new_anim->start_value += v_ofs;
			new_anim->end_value += v_ofs;
		}
		if ((new_anim->exec_cb != NULL) && (new_anim->var != NULL))
		{
			new_anim->exec_cb(new_anim->var, new_anim->start_value);
		}
	}
	wa_anim_mark_list_change();
}

/**
* anim set var
* @param anim
* @param var
*/
void wa_anim_set_var(wa_anim* anim, void* var)
{
	anim->var = var;
}

/**
* anim set exec_cb
* @param anim
* @param exec_cb
*/
void wa_anim_set_exec_cb(wa_anim* anim, wa_anim_exec_xcb cb)
{
	anim->exec_cb = cb;
}

/**
* anim set start_cb
* @param anim
* @param start_cb
*/
void wa_anim_set_start_cb(wa_anim* anim, wa_anim_start_cb cb)
{
	anim->start_cb = cb;
}

/**
* anim set finish_cb
* @param anim
* @param finish_cb
*/
void wa_anim_set_finish_cb(wa_anim* anim, wa_anim_finish_cb cb)
{
	anim->finish_cb = cb;
}

/**
* anim set deleted_cb
* @param anim
* @param deleted_cb
*/
void wa_anim_set_deleted_cb(wa_anim* anim, wa_anim_deleted_cb cb)
{
	anim->deleted_cb = cb;
}

/**
* anim set get_value_cb
* @param anim
* @param get_value_cb
*/
void wa_anim_set_get_value_cb(wa_anim* anim, wa_anim_get_value_cb cb)
{
	anim->get_value_cb = cb;
}

/**
* anim set path_cb
* @param anim
* @param path_cb
*/
void wa_anim_set_path_cb(wa_anim* anim, wa_anim_path_cb cb)
{
	anim->path_cb = cb;
}

/**
* anim set custom exec cb
* @param anim
* @param custom exec cb
*/
void wa_anim_set_custom_exec_cb(wa_anim* anim, wa_anim_custom_exec_cb cb)
{
	anim->var = anim;
	anim->exec_cb = (wa_anim_exec_xcb)cb;
}

/**
* anim set user data
* @param anim
* @param user data
*/
void wa_anim_set_user_data(wa_anim* anim, void* user_data)
{
	anim->user_data = user_data;
}

/**
* anim set values
* @param anim
* @param start
* @param end
*/
void wa_anim_set_values(wa_anim* anim, int32_t start, int32_t end)
{
	anim->start_value = start;
	anim->current_value = start;
	anim->end_value = end;
}

/**
* anim set time long
* @param anim
* @param time long
*/
void wa_anim_set_time_long(wa_anim* anim, int32_t time_long)
{
	anim->time_long = time_long;
}

/**
* anim set a delay before start
* @param anim
* @param delay
*/
void wa_anim_set_delay(wa_anim* anim, int32_t delay)
{
	anim->current_time = -delay;
}

/**
* anim set playback time
* @param anim
* @param playback time
*/
void wa_anim_set_playback_time(wa_anim* anim, uint32_t time)
{
	anim->playback_time = time;
}

/**
* anim set playback delay
* @param anim
* @param playback delay
*/
void wa_anim_set_playback_delay(wa_anim* anim, uint32_t delay)
{
	anim->playback_delay = delay;
}

/**
* anim set repeat delay
* @param anim
* @param repeat delay
*/
void wa_anim_set_repeat_delay(wa_anim* anim, uint32_t delay)
{
	anim->repeat_delay = delay;
}

/**
* anim set repeat cnt
* @param anim
* @param repeat cnt
*/
void wa_anim_set_repeat_cnt(wa_anim* anim, uint16_t cnt)
{
	anim->repeat_cnt = cnt;
}

/**
* anim set early apply
* @param anim
* @param early apply
*/
void wa_anim_set_early_apply(wa_anim* anim, wa_result early_apply)
{
	anim->early_apply = early_apply;
}

/**
* anim get remain time
* @param anim
* @return anim's remain time
*/
uint32_t wa_anim_get_remain_time(wa_anim* anim)
{
	uint32_t remain = WA_ANIM_PLAYTIME_INFINITE;

	if (anim->repeat_cnt == WA_ANIM_REPEAT_INFINITE)
	{
		return remain;
	}
	remain = anim->time_long - anim->current_time;
	if (anim->playback_now == wa_nok)
	{
		remain += anim->playback_delay + anim->playback_time;
	}
	if (anim->repeat_cnt <= 1)
	{
		return remain;
	}
	remain += (anim->repeat_delay + anim->time_long +
				anim->playback_delay + anim->playback_time) *
				(anim->repeat_cnt - 1);
	return remain;
}

/**
* anim refr now
*/
void wa_anim_refr_now(void)
{
	anim_task->cb(anim_task);
}

/**
* delete all anim
*/
void wa_anim_del_all(void)
{
	wa_ll_clear(&top_anim_ll);
	wa_anim_mark_list_change();
}

/**
 * Calculate the current value of an animation applying linear characteristic
 * @param a     pointer to an animation
 * @return      the current value to set
 */
int32_t wa_anim_path_linear(const wa_anim* a)
{
	/*Calculate the current step*/
	int32_t step = wa_anim_map(a->current_time, 0, a->time_long, 0, WA_ANIM_RESOLUTION);

	/*Get the new value which will be proportional to `step`
	 *and the `start` and `end` values*/
	int32_t new_value;
	new_value = step * (a->end_value - a->start_value);
	new_value = new_value >> WA_ANIM_RES_SHIFT;
	new_value += a->start_value;

	return new_value;
}

/**
 * Calculate the current value of an animation slowing down the start phase
 * @param a     pointer to an animation
 * @return      the current value to set
 */
int32_t wa_anim_path_ease_in(wa_anim* a)
{
	/*Calculate the current step*/
	uint32_t t = wa_anim_map(a->current_time, 0, a->time_long, 0, WA_BEZIER_VAL_MAX);
	int32_t step = wa_anim_bezier3(t, 0, 50, 100, WA_BEZIER_VAL_MAX);

	int32_t new_value;
	new_value = step * (a->end_value - a->start_value);
	new_value = new_value >> WA_BEZIER_VAL_SHIFT;
	new_value += a->start_value;

	return new_value;
}

/**
 * Calculate the current value of an animation slowing down the end phase
 * @param a     pointer to an animation
 * @return      the current value to set
 */
int32_t wa_anim_path_ease_out(wa_anim* a)
{
	/*Calculate the current step*/
	uint32_t t = wa_anim_map(a->current_time, 0, a->time_long, 0, WA_BEZIER_VAL_MAX);
	int32_t step = wa_anim_bezier3(t, 0, 900, 950, WA_BEZIER_VAL_MAX);

	int32_t new_value;
	new_value = step * (a->end_value - a->start_value);
	new_value = new_value >> WA_BEZIER_VAL_SHIFT;
	new_value += a->start_value;

	return new_value;
}

/**
 * Calculate the current value of an animation applying an "S" characteristic (cosine)
 * @param a     pointer to an animation
 * @return      the current value to set
 */
int32_t wa_anim_path_ease_in_out(wa_anim* a)
{
	/*Calculate the current step*/
	uint32_t t = wa_anim_map(a->current_time, 0, a->time_long, 0, WA_BEZIER_VAL_MAX);
	int32_t step = wa_anim_bezier3(t, 0, 50, 952, WA_BEZIER_VAL_MAX);

	int32_t new_value;
	new_value = step * (a->end_value - a->start_value);
	new_value = new_value >> WA_BEZIER_VAL_SHIFT;
	new_value += a->start_value;

	return new_value;
}

/**
 * Calculate the current value of an animation with overshoot at the end
 * @param a     pointer to an animation
 * @return      the current value to set
 */
int32_t wa_anim_path_overshoot(wa_anim* a)
{
	/*Calculate the current step*/
	uint32_t t = wa_anim_map(a->current_time, 0, a->time_long, 0, WA_BEZIER_VAL_MAX);
	int32_t step = wa_anim_bezier3(t, 0, 1000, 1300, WA_BEZIER_VAL_MAX);

	int32_t new_value;
	new_value = step * (a->end_value - a->start_value);
	new_value = new_value >> WA_BEZIER_VAL_SHIFT;
	new_value += a->start_value;

	return new_value;
}

/**
 * Calculate the current value of an animation with 3 bounces
 * @param a     pointer to an animation
 * @return      the current value to set
 */
int32_t wa_anim_path_bounce(wa_anim* a)
{
	/*Calculate the current step*/
	int32_t t = wa_anim_map(a->current_time, 0, a->time_long, 0, WA_BEZIER_VAL_MAX);
	int32_t diff = (a->end_value - a->start_value);

	/*3 bounces has 5 parts: 3 down and 2 up. One part is t / 5 long*/

	if (t < 408) {
		/*Go down*/
		t = (t * 2500) >> WA_BEZIER_VAL_SHIFT; /*[0..1024] range*/
	}
	else if (t >= 408 && t < 614) {
		/*First bounce back*/
		t -= 408;
		t = t * 5; /*to [0..1024] range*/
		t = WA_BEZIER_VAL_MAX - t;
		diff = diff / 20;
	}
	else if (t >= 614 && t < 819) {
		/*Fall back*/
		t -= 614;
		t = t * 5; /*to [0..1024] range*/
		diff = diff / 20;
	}
	else if (t >= 819 && t < 921) {
		/*Second bounce back*/
		t -= 819;
		t = t * 10; /*to [0..1024] range*/
		t = WA_BEZIER_VAL_MAX - t;
		diff = diff / 40;
	}
	else if (t >= 921 && t <= WA_BEZIER_VAL_MAX) {
		/*Fall back*/
		t -= 921;
		t = t * 10; /*to [0..1024] range*/
		diff = diff / 40;
	}

	if (t > WA_BEZIER_VAL_MAX) t = WA_BEZIER_VAL_MAX;
	if (t < 0) t = 0;
	int32_t step = wa_anim_bezier3(t, WA_BEZIER_VAL_MAX, 800, 500, 0);

	int32_t new_value;
	new_value = step * diff;
	new_value = new_value >> WA_BEZIER_VAL_SHIFT;
	new_value = a->end_value - new_value;

	return new_value;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

 /**
  * Get the mapped of a number given an input and output range
  * @param x integer which mapped value should be calculated
  * @param min_in min input range
  * @param max_in max input range
  * @param min_out max output range
  * @param max_out max output range
  * @return the mapped number
  */
static int32_t wa_anim_map(int32_t x, int32_t min_in, int32_t max_in, int32_t min_out, int32_t max_out)
{
	if (max_in >= min_in && x >= max_in) return max_out;
	if (max_in >= min_in && x <= min_in) return min_out;

	if (max_in <= min_in && x <= max_in) return max_out;
	if (max_in <= min_in && x >= min_in) return min_out;

	/**
	 * The equation should be:
	 *   ((x - min_in) * delta_out) / delta in) + min_out
	 * To avoid rounding error reorder the operations:
	 *   (x - min_in) * (delta_out / delta_min) + min_out
	 */

	int32_t delta_in = max_in - min_in;
	int32_t delta_out = max_out - min_out;

	return ((x - min_in) * delta_out) / delta_in + min_out;
}

/**
 * Calculate a value of a Cubic Bezier function.
 * @param t time in range of [0..LV_BEZIER_VAL_MAX]
 * @param u0 start values in range of [0..LV_BEZIER_VAL_MAX]
 * @param u1 control value 1 values in range of [0..LV_BEZIER_VAL_MAX]
 * @param u2 control value 2 in range of [0..LV_BEZIER_VAL_MAX]
 * @param u3 end values in range of [0..LV_BEZIER_VAL_MAX]
 * @return the value calculated from the given parameters in range of [0..LV_BEZIER_VAL_MAX]
 */
static uint32_t wa_anim_bezier3(uint32_t t, uint32_t u0, uint32_t u1, uint32_t u2, uint32_t u3)
{
	uint32_t t_rem = 1024 - t;
	uint32_t t_rem2 = (t_rem * t_rem) >> 10;
	uint32_t t_rem3 = (t_rem2 * t_rem) >> 10;
	uint32_t t2 = (t * t) >> 10;
	uint32_t t3 = (t2 * t) >> 10;

	uint32_t v1 = (t_rem3 * u0) >> 10;
	uint32_t v2 = (3 * t_rem2 * t * u1) >> 20;
	uint32_t v3 = (3 * t_rem * t2 * u2) >> 20;
	uint32_t v4 = (t3 * u3) >> 10;

	return v1 + v2 + v3 + v4;
}

/**
* anim task
*/
static void wa_anim_task(void* task)
{
	wa_anim* anim;
	uint32_t elaps = wa_tick_get_elap(anim_last_timer_run);

	anim_run_round = (anim_run_round == wa_nok) ? wa_ok : wa_nok;

	anim = wa_ll_get_head(&top_anim_ll);
	while (anim != NULL)
	{
		anim_list_changed = wa_nok;
		if (anim->run_round != anim_run_round)
		{
			int32_t new_act_time;
			anim->run_round = anim_run_round;
			new_act_time = anim->current_time + elaps;
			if ((anim->start_cb_called == wa_nok) && (anim->current_time <= 0) && (new_act_time >= 0))
			{
				if ((anim->early_apply == wa_nok) && (anim->get_value_cb != NULL))
				{
					int32_t v_ofs = anim->get_value_cb(anim);
					anim->start_value += v_ofs;
					anim->end_value += v_ofs;
				}
				if (anim->start_cb != NULL)
				{
					anim->start_cb(anim);
				}
				anim->start_cb_called = wa_ok;
			}

			anim->current_time += elaps;
			if (anim->current_time >= 0)
			{
				int32_t new_value;
				if (anim->current_time > anim->time_long)
				{
					anim->current_time = anim->time_long;
				}

				new_value = anim->path_cb(anim);

				if (new_value != anim->current_value)
				{
					anim->current_value = new_value;
					if (anim->exec_cb != NULL)
					{
						anim->exec_cb(anim->var, new_value);
					}
				}

				if (anim->current_time >= anim->time_long)
				{
					wa_anim_ready_handler(anim);
				}
			}
		}

		if (anim_list_changed == wa_ok)
		{
			anim = wa_ll_get_head(&top_anim_ll);
		}
		else
		{
			anim = wa_ll_get_next(&top_anim_ll, anim);
		}
	}
	anim_last_timer_run = wa_tick_get();
}

/**
* anim ready handler
*/
static void wa_anim_ready_handler(wa_anim* anim)
{
	if ((anim->playback_now == wa_nok) && (anim->repeat_cnt > 0) && (anim->repeat_cnt != WA_ANIM_REPEAT_INFINITE))
	{
		anim->repeat_cnt--;
	}
	if ((anim->repeat_cnt == 0) && ((anim->playback_time == 0) || (anim->playback_now == wa_ok)))
	{
		wa_ll_remove(&top_anim_ll, anim);
		wa_anim_mark_list_change();

		if (anim->finish_cb != NULL)
		{
			anim->finish_cb(anim);
		}
		if (anim->deleted_cb != NULL)
		{
			anim->deleted_cb(anim);
		}
		wa_free(anim);
	}
	else
	{
		anim->current_time = -(int32_t)(anim->repeat_delay);
		if (anim->playback_time != 0)
		{
			int32_t tmp;
			if (anim->playback_now == wa_nok)
			{
				anim->current_time = -(int32_t)(anim->playback_delay);
			}

			anim->playback_now = (anim->playback_now == wa_ok) ? wa_nok : wa_ok;

			tmp = anim->start_value;
			anim->start_value = anim->end_value;
			anim->end_value = tmp;
			tmp = anim->time_long;
			anim->time_long = anim->playback_time;
			anim->playback_time = tmp;
		}
	}
}

static void wa_anim_mark_list_change(void)
{
	anim_list_changed = wa_ok;
	if (wa_ll_get_head(&top_anim_ll) != NULL)
	{
		wa_task_start_timer(anim_task);
	}
	else
	{
		wa_task_stop_timer(anim_task);
	}
}

#endif
